#pragma once
#include <cassert>
#include <iostream>

#include <GLFW/glfw3.h>
#include <json.hpp>

#define MAKE_MAGIC(s, x, y) ((s << 16) + (x << 8) + (y << 0))

class UIElement;
class UIElementLoader {
public:
    static UIElement &GetElement(const std::string &name);

private:
    UIElementLoader(const char *filename);
    static inline UIElementLoader *ptr = nullptr;

    nlohmann::json json;
    std::map<std::string, std::unique_ptr<UIElement>> elements;
};

struct TextureData {
    std::string textureFilename;
    double modelWidth;
    double modelHeight;
    double textureX;
    double textureY;
    double textureWidth;
    double textureHeight;

    static struct TextureData GetByTexture(int size, int textureX, int textureY)
    {
        struct TextureData data = {
            .textureFilename = "out/war_of_tank.png",
            .modelWidth = 1.0 * size,
            .modelHeight = 1.0  * size,
            .textureX = 1.0  * textureX,
            .textureY = 1.0  * textureY,
            .textureWidth = 1.0 / 128 / 8 * size,
            .textureHeight = 1.0 / 52 / 8 * size,
        };
        data.textureX *= data.textureWidth;
        data.textureY = 1 - (data.textureY + 1) * data.textureHeight;
        return data;
    }
};

class Game;
class UIElement {
public:
    std::string name = "unset";
    double x = 0;
    double y = 0;
    double angle = 0;
    double angleAccelerate = 0;
    double speed = 0;
    double speedMax = 1e9;
    double accelerate = 0;
    double frictionAccelerate = 0;
    int size;
    int textureX;
    int textureY;
    bool canDestroy = true;
    bool canBlock = true;

    UIElement(const char *name, int size, int top, int left, bool canDestroy, bool canBlock)
        : name(name), size(size), textureX(left), textureY(top), canDestroy(canDestroy), canBlock(canBlock) {}

    UIElement(const UIElement &ele, int x, int y)
    {
        *this = ele;
        this->x = x;
        this->y = y;
    }

    virtual ~UIElement() = default;

    virtual struct TextureData GetTextureData()
    {
        return TextureData::GetByTexture(size, textureX, textureY);
    }

    virtual int AsInt() const
    {
        return MAKE_MAGIC(size, textureX, textureY);
    }

    virtual bool IsSameElementAs(const UIElement &ele) const
    {
        return AsInt() == ele.AsInt();
    }

    virtual void OnKey(Game &game, int32_t key, int32_t action, int32_t mode) {}
    virtual void OnMouseButton(Game &game, int button, int action, int mods) {}
    virtual void OnMousePosition(Game &game, int x, int y) {}

    bool IsIntersectWith(const UIElement &ele) const
    {
        auto &size1 = ele.size;
        auto &size2 = size;
        return IsContain(ele.x, ele.y) || IsContain(ele.x + size1, ele.y + size1)
            || IsContain(ele.x + size1, ele.y) || IsContain(ele.x, ele.y + size1)
            || ele.IsContain(x, y) || ele.IsContain(x + size2, y + size2)
            || ele.IsContain(x + size2, y) || ele.IsContain(x, y + size2);
    }

    bool IsContain(int pointX, int pointY) const
    {
        return x < pointX && pointX < x + size && y < pointY && pointY < y + size;
    }

    virtual void Update()
    {
        x += speed * sin(angle);
        y += speed * cos(angle);

        speed += accelerate - frictionAccelerate;
        speed = speed < 0 ? 0 : speed;
        speed = speed > speedMax ? speedMax : speed;
        angle += angleAccelerate;
    }
};

class UIElementTank : public UIElement {
public:
    static constexpr double SpeedUpAccelerate = 0.05;
    static constexpr double SpeedDownAccelerate = -0.1;
    static constexpr double RotateLeftDownAccelerate = -0.05;
    static constexpr double RotateRightDownAccelerate = -RotateLeftDownAccelerate;
    static constexpr double FrictionAccelerate = 0.01;
    static constexpr double SpeedMax = 10;

    int direction = GLFW_KEY_UP;
    bool moved = false;
    int currentCooldown = 30;
    static constexpr int fireCooldown = 3;
    int lastMouseAction;
    int lastMouseButton;
    int lastMouseX;
    int lastMouseY;

    UIElementTank(const char *name, int size, int top, int left, bool canDestroy, bool canBlock)
        : UIElement(name, size, top, left, canDestroy, canBlock)
    {
        frictionAccelerate = FrictionAccelerate;
        speedMax = SpeedMax;
    }

    UIElementTank(const UIElement &ele, int x, int y) : UIElement(ele, x, y)
    {
        frictionAccelerate = FrictionAccelerate;
        speedMax = SpeedMax;
    }

    void OnKey(Game &game, int32_t key, int32_t action, int32_t mode) override;
    void OnMouseButton(Game &game, int button, int action, int mods) override;
    void OnMousePosition(Game &game, int x, int y) override;
    void Update() override
    {
        UIElement::Update();
        currentCooldown--;
    }
};

class UIElementBullet : public UIElement {
public:
    UIElementBullet(const char *name, int size, int top, int left, bool canDestroy, bool canBlock)
        : UIElement(name, size, top, left, canDestroy, canBlock)
    {
        speed = 4;
    }

    UIElementBullet(const UIElement &ele, int x, int y) : UIElement(ele, x, y)
    {
        speed = 4;
    }

    void Init()
    {
        auto dx = 20 * sqrt(2) * sin(angle);
        auto dy = 20 * sqrt(2) * cos(angle);
        dx = dx > 20 ? 20 : dx < -20 ? -20 : dx;
        dy = dy > 20 ? 20 : dy < -20 ? -20 : dy;
        x += dx;
        y += dy;
    }
};
